package com.zwh.se.collection.concurrent;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author yexi
 * COW类并发容器：全称为Copy On Write容器,写时复制容器。(读写分离容器)
 * 原理：向容器中添加元素时，先将容器进行Copy复制出一个新容器，然后将元素添加到新容器中，再将原容器的引用指向新容器。
 *     这样的话在并发读的时候不需要锁定容器，因为原容器没有变化，所以可以读取原容器中的值，使用的是一种读写分离的思想。
 * 设计好处：对原数组的操作本身是无锁的,而仅仅在做写操作时对写操作本身加锁。因此无论是多少个线程来读数组,都能读取到旧数组的值且效率不会降低。
 * 缺陷：从好处中也能看出来,这种容器只能保证最终一致性,而不能保证实时一致性。所以如果你希望写入的数据能马上读到，请不要使用CopyOnWrite容器。
 * 适合场景：也就是读多写少的场景。而且由于其写操作的实现是每次长度只+1,所以频繁写会导致JVM内存消耗飙升。只建议在高并发读的场景下使用。
 */
@SuppressWarnings("all")
public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
        //添加方法：
        copyOnWriteArrayList.add(1);
        copyOnWriteArrayList.add(2);
        copyOnWriteArrayList.add(3);
        copyOnWriteArrayList.add(4);
        //[1, 2, 3, 4]
        System.out.println(copyOnWriteArrayList);
        //add方法无论元素是否存在，都可以添加进去-->添加重复的元素
        copyOnWriteArrayList.add(3);
        //[1, 2, 3, 4, 3]
        System.out.println(copyOnWriteArrayList);
        //Absent：意指 缺席的；缺少的
        //添加不存在的元素-->不可以添加重复的数据
        copyOnWriteArrayList.addIfAbsent(4);
        //[1, 2, 3, 4, 3]
        System.out.println(copyOnWriteArrayList);
        copyOnWriteArrayList.addIfAbsent(44);
        //[1, 2, 3, 4, 3, 44]
        System.out.println(copyOnWriteArrayList);

        System.out.println("----------------------CopyOnWriteArrayList的底层分析----------------------");
        /**
         * @see CopyOnWriteArrayList#CopyOnWriteArrayList()
         *         setArray(new Object[0]);
         * @see CopyOnWriteArrayList#array 底层基于Object数组实现
         * @see CopyOnWriteArrayList#setArray(Object[] a)
         *         array = a;  所以初始化时就是 array = new Object[0]
         */
        /**
         * @see CopyOnWriteArrayList#add(Object)
         *         final ReentrantLock lock = this.lock;  //使用ReentrantLock保证线程安全
         *         lock.lock();
         *         try {
         *             Object[] elements = getArray();  // return array --> 返回底层数组,赋值给elements
         *             int len = elements.length;      //获取老数组的长度
         *             Object[] newElements = Arrays.copyOf(elements, len + 1); //将老数组元素复制到新数组,新数组长度+1
         *             newElements[len] = e;          //将新加入的e元素放入新数组最后位置
         *             setArray(newElements);         // 底层数组 array 指向新数组
         *             return true;
         *         } finally {
         *             lock.unlock();
         *         }
         */
        copyOnWriteArrayList.add(3);
        /**
         * @see CopyOnWriteArrayList#addIfAbsent(Object e)
         *         Object[] snapshot = getArray();  // return array --> 返回底层数组,赋值给snapshot
         *         //遍历查看该元素是否已存在,已存在则返回false
         *         return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
         *             addIfAbsent(e, snapshot);
         *
         * @param o 要搜索的元素
         * @param elements 底层数组
         * @param index 要搜索的第一个索引
         * @param fence 最后一个要搜索的索引
         * @see CopyOnWriteArrayList#indexOf(Object o, Object[] elements, int index, int fence)
         *         if (o == null) {
         *             for (int i = index; i < fence; i++)
         *                 if (elements[i] == null)
         *                     return i;
         *         } else {
         *             for (int i = index; i < fence; i++)
         *                 if (o.equals(elements[i]))
         *                     return i;
         *         }
         *         return -1;
         *
         * @param e 新增的元素
         * @param snapshot 在进入这个方法之前就获取的底层数组
         * @see CopyOnWriteArrayList#addIfAbsent(Object e, Object[] snapshot)
         *        final ReentrantLock lock = this.lock;  //使用ReentrantLock保证线程安全
         *         lock.lock();
         *         try {
         *             Object[] current = getArray();  // return array --> 返回底层数组,赋值给current
         *             int len = current.length;
         *             //如果之前获取的底层数组跟现在的底层数组不一样了(线程不安全情况时出现),就再遍历比较一次
         *             //  举例:使用snapshot计算索引时底层数组执行删除了,那么此时current就比snapshot长度小
         *             //       随后,Math.min后common取current长度,for遍历在current里找重复,
         *             //       由于current比较小,所以此时其实判断就完成了。后面的indexOf则是用于线程不安全时发生插入事件了。
         *             if (snapshot != current) {
         *                 // Optimize for lost race to another addXXX operation
         *                 int common = Math.min(snapshot.length, len);
         *                 for (int i = 0; i < common; i++)
         *                 //eq(e, current[i])将放入的元素和老数组的每一个元素进行比较，如果有重复的元素，就返回false，不添加了
         *                     if (current[i] != snapshot[i] && eq(e, current[i]))
         *                         return false;
         *                 //如果线程不安全的情况是删除的话,此判断无意义。如果是新增的话,由于snapshot较短,从而common比len小,
         *                 //因此后面多出来的部分也需要判断一次才可以
         *                 if (indexOf(e, current, common, len) >= 0)
         *                         return false;
         *             }
         *             Object[] newElements = Arrays.copyOf(current, len + 1);  //将老数组元素复制到新数组,新数组长度+1
         *             newElements[len] = e;       //将新加入的e元素放入新数组最后位置
         *             setArray(newElements);      // 底层数组 array 指向新数组
         *             return true;
         *         } finally {
         *             lock.unlock();
         *         }
         *
         */
        copyOnWriteArrayList.addIfAbsent(4);

    }
}
